home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-04
/
bipl.zip
/
PROCS.ZIP
/
LSCAN.ICN
< prev
next >
Wrap
Text File
|
1992-09-28
|
15KB
|
547 lines
############################################################################
#
# File: lscan.icn
#
# Subject: Procedures for quasi scanning routines for lists
#
# Author: Richard L. Goerwitz
#
# Date: June 3, 1991
#
###########################################################################
#
# Version: 1.20
#
###########################################################################
#
# Purpose: String scanning is terrific, but often I am forced to
# tokenize and work with lists. So as to make operations on these
# lists as close to corresponding string operations as possible, I've
# implemented a series of list analogues to any(), bal(), find(),
# many(), match(), move(), pos(), tab(), and upto(). Their names are
# just like corresponding string functions, except with a prepended
# "l_" (e.g. l_any()). Functionally, the list routines parallel the
# string ones closely, except that in place of strings, l_find and
# l_match accept lists as their first argument. L_any(), l_many(),
# and l_upto() all take either sets of lists or lists of lists (e.g.
# l_tab(l_upto([["a"],["b"],["j","u","n","k"]])). Note that l_bal(),
# unlike the builtin bal(), has no defaults for the first four
# arguments. This just seemed appropriate, given that no precise
# list analogue to &cset, etc. occurs.
#
# The default subject for list scans (analogous to &subject) is
# l_SUBJ. The equivalent of &pos is l_POS. Naturally, these
# variables are both global. They are used pretty much like &subject
# and &pos, except that they are null until a list scanning
# expression has been encountered containing a call to l_Bscan() (on
# which, see below).
#
# Note that environments cannot be maintained quite as elegantly as
# they can be for the builtin string-scanning functions. One must
# use instead a set of nested procedure calls, as explained in the
# _Icon Analyst_ 1:6 (June, 1991), p. 1-2. In particular, one cannot
# suspend, return, or otherwise break out of the nested procedure
# calls. They can only be exited via failure. The names of these
# procedures, at least in this implementation, are l_Escan and
# l_Bscan. Here is one example of how they might be invoked:
#
# suspend l_Escan(l_Bscan(some_list_or_other), {
# l_tab(10 to *l_SUBJ) & {
# if l_any(l1) | l_match(l2) then
# old_l_POS + (l_POS-1)
# }
# })
#
# Note that you cannot do this:
#
# l_Escan(l_Bscan(some_list_or_other), {
# l_tab(10 to *l_SUBJ) & {
# if l_any(l1) | l_match(l2) then
# suspend old_l_POS + (l_POS-1)
# }
# })
#
# Remember, it's no fair to use suspend within the list scanning
# expression. l_Escan must do all the suspending. It is perfectly OK,
# though, to nest well-behaved list scanning expressions. And they can
# be reliably used to generate a series of results as well.
#
############################################################################
#
# Here's another simple example of how one might invoke the l_scan
# routines:
#
# procedure main()
#
# l := ["h","e","l","l","o"," ","t","t","t","h","e","r","e"]
#
# l_Escan(l_Bscan(l), {
# hello_list := l_tab(l_match(["h","e","l","l","o"]))
# every writes(!hello_list)
# write()
#
# # Note the nested list-scanning expressions.
# l_Escan(l_Bscan(l_tab(0)), {
# l_tab(l_many([[" "],["t"]]) - 1)
# every writes(!l_tab(0))
# write()
# })
# })
#
# end
#
# The above program simply writes "hello" and "there" on successive
# lines to the standard output.
#
############################################################################
#
# PITFALLS: In general, note that we are comparing lists here instead
# of strings, so l_find("h", l), for instance, will yield an error
# message (use l_find(["h"], l) instead). The point at which I
# expect this nuance will be most confusing will be in cases where
# one is looking for lists within lists. Suppose we have a list,
#
# l1 := ["junk",[["hello"]," ",["there"]],"!","m","o","r","e","junk"]
#
# and suppose, moreover, that we wish to find the position in l1 at
# which the list
#
# [["hello"]," ",["there"]]
#
# occurs. If, say, we assign [["hello"]," ",["there"]] to the
# variable l2, then our l_find() expression will need to look like
#
# l_find([l2],l1)
#
############################################################################
#
# Extending scanning to lists is really very difficult. What I think
# (at least tonight) is that scanning should never have been
# restricted to strings. It should have been designed to operate on
# all homogenous one-dimensional arrays (vectors, for you LISPers).
# You should be able, in other words, to scan vectors of ints, longs,
# characters - any data type that seems useful. The only question in
# my mind is how to represent vectors as literals. Extending strings
# to lists goes beyond the bounds of scanning per-se. This library is
# therefore something of a stab in the dark.
#
############################################################################
global l_POS
global l_SUBJ
record l_ScanEnvir(subject,pos)
procedure l_Bscan(e1)
#
# Prototype list scan initializer. Based on code published in
# the _Icon Analyst_ 1:6 (June, 1991), p. 1-2.
#
local l_OuterEnvir
initial {
l_SUBJ := []
l_POS := 1
}
#
# Save outer scanning environment.
#
l_OuterEnvir := l_ScanEnvir(l_SUBJ, l_POS)
#
# Set current scanning environment to subject e1 (arg 1). Pos
# defaults to 1. Suspend the saved environment. Later on, the
# l_Escan procedure will need this in case the scanning expres-
# sion as a whole sends a result back to the outer environment,
# and the outer environment changes l_SUBJ and l_POS.
#
l_SUBJ := e1
l_POS := 1
suspend l_OuterEnvir
#
# Restore the saved environment (plus any changes that might have
# been made to it as noted in the previous run of comments).
#
l_SUBJ := l_OuterEnvir.subject
l_POS := l_OuterEnvir.pos
#
# Signal failure of the scanning expression (we're done producing
# results if we get to here).
#
fail
end
procedure l_Escan(l_OuterEnvir, e2)
local l_InnerEnvir
#
# Set the inner scanning environment to the values assigned to it
# by l_Bscan. Remember that l_SUBJ and l_POS are global. They
# don't need to be passed as parameters from l_Bscan. What
# l_Bscan() needs to pass on is the l_OuterEnvir record,
# containing the values of l_SUBJ and l_POS before l_Bscan() was
# called. l_Escan receives this "outer environment" as its first
# argument, l_OuterEnvir.
#
l_InnerEnvir := l_ScanEnvir(l_SUBJ, l_POS)
#
# Whatever expression produced e2 has passed us a result. Now we
# restore l_SUBJ and l_POS, and send that result back to the outer
# environment.
#
l_SUBJ := l_OuterEnvir.subject
l_POS := l_OuterEnvir.pos
suspend e2
#
# Okay, we've resumed to (attempt to) produce another result. Re-
# store the inner scanning environment (the one we're using in the
# current scanning expression). Remember? It was saved in l_Inner-
# Envir just above.
#
l_SUBJ := l_InnerEnvir.subject
l_POS := l_InnerEnvir.pos
#
# Fail so that the second argument (the one that produced e2) gets
# resumed. If it fails to produce another result, then the first
# argument is resumed, which is l_Bscan(). If l_Bscan is resumed, it
# will restore the outer environment and fail, causing the entire
# scanning expression to fail.
#
fail
end
procedure l_move(i)
/i & stop("l_move: Null argument.")
if /l_POS | /l_SUBJ then
stop("l_move: Call l_Bscan() first.")
#
# Sets l_POS to l_POS+i; suspends that portion of l_SUBJ extending
# from the old l_POS to the new one. Resets l_POS if resumed,
# just the way matching procedures are supposed to. Fails if l_POS
# plus i is larger than l_SUBJ+1 or if l_POS+i is less than 1.
#
suspend l_SUBJ[.l_POS:l_POS <- (0 < (*l_SUBJ+1 >= l_POS+i))]
end
procedure l_tab(i)
/i & stop("l_tab: Null argument.")
if /l_POS | /l_SUBJ then
stop("l_tab: Call l_Bscan() first.")
if i <= 0
then suspend l_SUBJ[.l_POS:l_POS <- 0 < (*l_SUBJ+1 >= (*l_SUBJ+1)+i)]
else suspend l_SUBJ[.l_POS:l_POS <- 0 < (*l_SUBJ+1 >= i)]
end
procedure l_any(l1,l2,i,j)
#
# Like any(c,s2,i,j) except that the string & cset arguments are
# replaced by list arguments. l1 must be a list of one-element
# lists, while l2 can be any list (l_SUBJ by default).
#
local sub_l, x
/l1 & stop("l_any: Null first argument!")
if type(l1) == "set" then l1 := sort(l1)
/l2 := l_SUBJ
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := \l_POS | 1
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
(i+1) > j & i :=: j
every sub_l := !l1 do {
if not (type(sub_l) == "list", *sub_l = 1) then
stop("l_any: Elements of l1 must be lists of length 1.")
# Let l_match check to see if i+1 is out of range.
if x := l_match(sub_l,l2,i,i+1) then
return x
}
end
procedure l_match(l1,l2,i,j)
#
# Analogous to match(s1,s2,i,j), except that s1 and s2 are lists,
# and l_match returns the next position in l2 after that portion
# (if any) which is structurally identical to l1. If a match is not
# found, l_match fails.
#
if /l1
then stop("l_match: Null first argument!")
if type(l1) ~== "list"
then stop("l_match: Call me with a list as the first arg.")
/l2 := l_SUBJ
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := \l_POS | 1
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
i + *l1 > j & i :=: j
i + *l1 > j & fail
if l_comp(l1,l2[i+:*l1]) then
return i + *l1
end
procedure l_comp(l1,l2)
#
# List comparison routine basically taken from Griswold & Griswold
# (1st ed.), p. 174.
#
local i
/l1 | /l2 & stop("l_comp: Null argument!")
l1 === l2 & (return l2)
if type(l1) == type(l2) == "list" then {
*l1 ~= *l2 & fail
every i := 1 to *l1
do l_comp(l1[i],l2[i]) | fail
return l2
}
end
procedure l_find(l1,l2,i,j)
#
# Like the builtin find(s1,s2,i,j), but for lists.
#
local x, old_l_POS
/l1 & stop("l_find: Null first argument!")
/l2 := l_SUBJ
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := \l_POS | 1
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
#
# See l_upto() below for a discussion of why things have to be done
# in this manner.
#
old_l_POS := l_POS
suspend l_Escan(l_Bscan(l2[i:j]), {
l_tab(1 to *l_SUBJ) & {
if l_match(l1) then
old_l_POS + (l_POS-1)
}
})
end
procedure l_upto(l1,l2,i,j)
#
# See l_any() above. This procedure just moves through l2, calling
# l_any() for each member of l2[i:j].
#
local old_l_POS
/l1 & stop("l_upto: Null first argument!")
if type(l1) == "set" then l1 := sort(l1)
/l2 := l_SUBJ
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := \l_POS | 1
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
#
# Save the old pos, then try arb()ing through the list to see if we
# can do an l_any(l1) at any position.
#
old_l_POS := l_POS
suspend l_Escan(l_Bscan(l2[i:j]), {
l_tab(1 to *l_SUBJ) & {
if l_any(l1) then
old_l_POS + (l_POS-1)
}
})
#
# Note that it WILL NOT WORK if you say:
#
# l_Escan(l_Bscan(l2[i:j]), {
# l_tab(1 to *l_SUBJ) & {
# if l_any(l1) then
# suspend old_l_POS + (l_POS-1)
# }
# })
#
# If we are to suspend a result, l_Escan must suspend that result.
# Otherwise scanning environments are not saved and/or restored
# properly.
#
end
procedure l_many(l1,l2,i,j)
local x, old_l_POS
/l1 & stop("l_many: Null first argument!")
if type(l1) == "set" then l1 := sort(l1)
/l2 := l_SUBJ
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := \l_POS | 1
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
#
# L_many(), like many(), is not a generator. We can therefore
# save one final result in x, and then later return (rather than
# suspend) that result.
#
old_l_POS := l_POS
l_Escan(l_Bscan(l2[i:j]), {
while l_tab(l_any(l1))
x := old_l_POS + (l_POS-1)
})
#
# Fails if there was no positional change (i.e. l_any() did not
# succeed even once).
#
return old_l_POS ~= x
end
procedure l_pos(i)
local x
if /l_POS | /l_SUBJ
then stop("l_move: Call l_Bscan() first.")
if i <= 0
then x := 0 < (*l_SUBJ+1 >= (*l_SUBJ+1)+i) | fail
else x := 0 < (*l_SUBJ+1 >= i) | fail
if x = l_POS
then return x
else fail
end
procedure l_bal(l1,l2,l3,l,i,j)
local l2_count, l3_count, x, position
/l1 & stop("l_bal: Null first argument!")
if type(l1) == "set" then l1 := sort(l1) # convert to a list
if type(l2) == "set" then l1 := sort(l2)
if type(l3) == "set" then l1 := sort(l3)
/l2 := l_SUBJ
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := \l_POS | 1
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
l2_count := l3_count := 0
every x := i to j-1 do {
if l_any(l2, l, x, x+1) then {
l2_count +:= 1
}
if l_any(l3, l, x, x+1) then {
l3_count +:= 1
}
if l2_count = l3_count then {
if l_any(l1,l,x,x+1)
then suspend x
}
}
end